﻿using System;
using System.Reflection;
using System.Security;
using System.Security.Permissions;

namespace Sencha.Serialization
{
	static class ReflectionExtentions
	{
		private static ReflectionPermission UnrestrictedReflection = new ReflectionPermission(PermissionState.Unrestricted);
		private static readonly MethodInfo PreserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
		private static readonly object[] EmptyArgs = new object[0];

		public static bool IsInstantiationOf(this Type type, Type openGenericType)
		{
			if (type == null)
				throw new ArgumentNullException("type");
			if (openGenericType == null)
				throw new ArgumentNullException("instantiationOfType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (openGenericType.IsGenericType && !openGenericType.IsGenericTypeDefinition)
				throw new ArgumentException(Loc.GetErrorString("Exception_ArgumentReqOpenGenericType", openGenericType));

			Type genericType = type;
			if (type.IsGenericType)
			{

				if (type.IsGenericType && !type.IsGenericTypeDefinition)
					genericType = type.GetGenericTypeDefinition();

				if (genericType == openGenericType || genericType.IsSubclassOf(openGenericType))
					return true;
			}
			// clean
			genericType = null;

			// check interfaces
			foreach (Type interfc in type.GetInterfaces())
			{
				genericType = interfc;

				if (!interfc.IsGenericType)
					continue;

				if (!interfc.IsGenericTypeDefinition)
					genericType = interfc.GetGenericTypeDefinition();

				if (genericType == openGenericType || genericType.IsSubclassOf(openGenericType))
					return true;
			}

			if (type.BaseType != null && type.BaseType != typeof(object))
				return IsInstantiationOf(type.BaseType, openGenericType);

			return false;
		}
		public static bool HasMultipleInstantiations(this Type type, Type openGenericType)
		{
			if (type == null)
				throw new ArgumentNullException("type");
			if (openGenericType == null)
				throw new ArgumentNullException("instantiationOfType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (openGenericType.IsGenericType && !openGenericType.IsGenericTypeDefinition)
				throw new ArgumentException(Loc.GetErrorString("Exception_ArgumentReqOpenGenericType", openGenericType));

			// can't has multiple implementations of class
			if (!openGenericType.IsInterface)
				return false;

			var found = 0;

			Type genericType = type;
			if (type.IsGenericType)
			{

				if (type.IsGenericType && !type.IsGenericTypeDefinition)
					genericType = type.GetGenericTypeDefinition();

				if (genericType == openGenericType || genericType.IsSubclassOf(openGenericType))
					found++;
			}
			// clean
			genericType = null;

			// check interfaces
			foreach (Type interfc in type.GetInterfaces())
			{
				genericType = interfc;

				if (!interfc.IsGenericType)
					continue;

				if (!interfc.IsGenericTypeDefinition)
					genericType = interfc.GetGenericTypeDefinition();

				if (genericType == openGenericType || genericType.IsSubclassOf(openGenericType))
					found++;
			}
			

			return found > 1;
		}
		public static Type[] GetInstantiationArguments(this Type type, Type openGenericType)
		{
			if (type == null)
				throw new ArgumentNullException("type");
			if (openGenericType == null)
				throw new ArgumentNullException("instantiationOfType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (openGenericType.IsGenericType && !openGenericType.IsGenericTypeDefinition)
				throw new ArgumentException(Loc.GetErrorString("Exception_ArgumentReqOpenGenericType", openGenericType));

			Type genericType = type;
			if (type.IsGenericType)
			{
				if (type.IsGenericType && !type.IsGenericTypeDefinition)
					genericType = type.GetGenericTypeDefinition();

				if (genericType == openGenericType || genericType.IsSubclassOf(openGenericType))
					return type.GetGenericArguments();
			}

			// clean
			genericType = null;

			// check interfaces
			foreach (Type _interface in type.GetInterfaces())
			{
				genericType = _interface;

				if (!_interface.IsGenericType)
					continue;

				if (!_interface.IsGenericTypeDefinition)
					genericType = _interface.GetGenericTypeDefinition();


				if (genericType == openGenericType || genericType.IsSubclassOf(openGenericType))
					return _interface.GetGenericArguments();
			}


			if (type.BaseType != null && type.BaseType != typeof(object))
				return GetInstantiationArguments(type.BaseType, openGenericType);

			return null;
		}

#if NET40
		[SecuritySafeCritical]
#endif
		public static bool IsReflectionPermissionGranted()
		{
#if NET35
			bool isGranted = System.Security.SecurityManager.IsGranted(UnrestrictedReflection);
#else
			bool isGranted = System.AppDomain.CurrentDomain.IsFullyTrusted;
#endif
			return isGranted;

		}
		public static void TryToPreserveStackTrace(this Exception e)
		{
			if (e == null)
				throw new ArgumentNullException("e");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (PreserveStackTrace != null)
			{
				bool isGranted = IsReflectionPermissionGranted();

				if (!isGranted)
					return;

				PreserveStackTrace.Invoke(e, EmptyArgs);
			}
		}
	}
}
